/* This file is part of REWise.
 *
 * REWise is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * REWise is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include <stddef.h>
#include <string.h>
#include <errno.h>

#ifdef __linux__
#include <linux/limits.h>
#else
#include <limits.h> // *BSD and MSYS2
#endif


#include "wisescript.h"
#include "reader.h"
#include "print.h"


static int WiseScriptSTOP = 0;


REWError readWiseScriptHeader(FILE * fp, WiseScriptHeader * header) {
  // init struct
  header->logPath = NULL;
  header->font    = NULL;

  REWError status;

  status = readBytesInto(fp, header->unknown_44, 44);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &header->logPath);
  if (status != REWERR_OK) {
    printError("Failed to read WiseScriptHeader logpath: %d\n", status);
    return status;
  }

  status = readString(fp, &header->font);
  if (status != REWERR_OK) {
    printError("Failed to read WiseScriptHeader font: %d\n", status);
    freeWiseScriptHeader(header);
    return status;
  }

  status = readBytesInto(fp, header->unknown_14, 14);
  if (status != REWERR_OK) {
    freeWiseScriptHeader(header);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptTexts(FILE * fp, WiseScriptTexts * texts) {
  REWError status;
  uint32_t i;

  // Init text pointers
  for (i = 0; i < 56; i++) {
    texts->installTexts[i] = NULL;
  }

  // Read the text strings
  for (i = 0; i < 56; i++) {
    status = readString(fp, &texts->installTexts[i]);
    if (status != REWERR_OK) {
      freeWiseScriptTexts(texts);
      return status;
    }
  }

  return REWERR_OK;
}


REWError readWiseScriptFileHeader(FILE * fp, WiseScriptFileHeader * data) {
  REWError status;

  // init struct
  data->destFile = NULL;
  data->fileText = NULL;

  status = readBytesInto(fp, data->unknown_2, 2);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->deflateStart);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->deflateEnd);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt16(fp, &data->date);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt16(fp, &data->time);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->inflatedSize);
  if (status != REWERR_OK) {
    return status;
  }

  status = readBytesInto(fp, data->unknown_20, 20);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->crc32);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->destFile);
  if (status != REWERR_OK) {
    printError("readWiseScriptFileHeader failed to read destFile: %d\n",
               status);
    return status;
  }

  // data->destFile is just 0x00
  if (data->destFile == NULL) {
    printError("readWiseScriptFileHeader destFile is a empty string\n");
    return REWERR_INVALDAT;
  }

  // parse filepath
  char * filePath = wiseScriptParsePath(data->destFile);
  if (filePath == NULL) {
    printError("readWiseScriptFileHeader invalid destFile\n");
    freeWiseScriptFileHeader(data);
    return REWERR_INVALDAT;
  }
  free(data->destFile);
  data->destFile = filePath;

  status = readString(fp, &data->fileText);
  if (status != REWERR_OK) {
    printError("readWiseScriptFileHeader failed to read fileText: "
               "%d\n", status);
    freeWiseScriptFileHeader(data);
    return status;
  }

  status = readBytesInto(fp, &data->terminator, 1);
  if (status != REWERR_OK) {
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x03(FILE * fp, WiseScriptUnknown0x03 * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x03 failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x03 failed to read unknownString_2: "
               "%d\n", status);
    freeWiseScriptUnknown0x03(data);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x04(FILE * fp, WiseScriptUnknown0x04 * data) {
  REWError status;

  // init struct
  data->dataString = NULL;

  status = readBytesInto(fp, &data->no, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->dataString);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x04 failed to read dataString: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x05(FILE * fp, WiseScriptUnknown0x05 * data) {
  REWError status;

  // init struct
  data->file = NULL;
  data->section = NULL;
  data->values = NULL;

  status = readString(fp, &data->file);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x05 failed to read file: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->section);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x05 failed to read section: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->values);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x05 failed to read values: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x06(FILE * fp, WiseScriptUnknown0x06 * data) {
  REWError status;

  status = readBytesInto(fp, data->unknown, 6);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->deflateStart);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->deflateEnd);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->inflatedSize);
  if (status != REWERR_OK) {
    return status;
  }

  status = readBytesInto(fp, &data->unknown1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x07(FILE * fp, WiseScriptUnknown0x07 * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;
  data->unknownString_3 = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x07 failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x07 failed to read unknownString_2: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_3);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x07 failed to read unknownString_3: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x08(FILE * fp, WiseScriptUnknown0x08 * data) {
  REWError status;
  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }
  return REWERR_OK;
}


REWError readWiseScriptUnknown0x09(FILE * fp, WiseScriptUnknown0x09 * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;
  data->unknownString_3 = NULL;
  data->unknownString_4 = NULL;
  data->unknownString_5 = NULL;
  data->unknown_2       = 0;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  if (data->unknown_1 != 0x01 && data->unknown_1 != 0x20) {
    status = readBytesInto(fp, &data->unknown_2, 1);
    if (status != REWERR_OK) {
      return status;
    }
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x09 failed to read unknownString_1: "
                 "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x09 failed to read unknownString_2: "
                 "%d\n", status);
    freeWiseScriptUnknown0x09(data);
    return status;
  }

  status = readString(fp, &data->unknownString_3);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x09 failed to read unknownString_3: "
                 "%d\n", status);
    freeWiseScriptUnknown0x09(data);
    return status;
  }

  status = readString(fp, &data->unknownString_4);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x09 failed to read unknownString_4: "
                 "%d\n", status);
    freeWiseScriptUnknown0x09(data);
    return status;
  }

  if (data->unknown_1 == 0x01 || data->unknown_1 == 0x20) {
    status = readString(fp, &data->unknownString_5);
    if (status != REWERR_OK) {
      printError("readWiseScriptUnknown0x09 failed to read unknownString_5: "
                 "%d\n", status);
      freeWiseScriptUnknown0x09(data);
      return status;
    }
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x0A(FILE * fp, WiseScriptUnknown0x0A * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;
  data->unknownString_3 = NULL;

  status = readBytesInto(fp, data->unknown_2, 2);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x0A failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x0A failed to read unknownString_2: "
               "%d\n", status);
    freeWiseScriptUnknown0x0A(data);
    return status;
  }

  status = readString(fp, &data->unknownString_3);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x0A failed to read unknownString_3: "
               "%d\n", status);
    freeWiseScriptUnknown0x0A(data);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x0B(FILE * fp, WiseScriptUnknown0x0B * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x0B failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x0C(FILE * fp, WiseScriptUnknown0x0C * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x0C failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    freeWiseScriptUnknown0x0C(data);
    printError("readWiseScriptUnknown0x0C failed to read unknownString_2: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x11(FILE * fp, WiseScriptUnknown0x11 * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x11 failed to read unknownString_1: "
               "%d\n", status);
    freeWiseScriptUnknown0x11(data);
    return status;
  }
  return REWERR_OK;
}


REWError readWiseScriptUnknown0x12(FILE * fp, WiseScriptUnknown0x12 * data) {
  REWError status;

  // init struct
  data->sourceFile = NULL;
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;
  data->destFile = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readBytesInto(fp, data->unknown_41, 41);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->sourceFile);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x12 failed to read sourceFile: %d\n",
               status);
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    freeWiseScriptUnknown0x12(data);
    printError("readWiseScriptUnknown0x12 failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    freeWiseScriptUnknown0x12(data);
    printError("readWiseScriptUnknown0x12 failed to read unknownString_2: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->destFile);
  if (status != REWERR_OK) {
    freeWiseScriptUnknown0x12(data);
    printError("readWiseScriptUnknown0x12 failed to read destFile: %d\n",
               status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x14(FILE * fp, WiseScriptUnknown0x14 * data) {
  REWError status;

  // init struct
  data->name = NULL;
  data->message = NULL;

  status = readUInt32(fp, &data->deflateStart);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->deflateEnd);
  if (status != REWERR_OK) {
    return status;
  }

  status = readUInt32(fp, &data->inflatedSize);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->name);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x14 failed to read name: %d\n", status);
    return status;
  }

  status = readString(fp, &data->message);
  if (status != REWERR_OK) {
    freeWiseScriptUnknown0x14(data);
    printError("readWiseScriptUnknown0x14 failed to read message: %d\n",
               status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x15(FILE * fp, WiseScriptUnknown0x15 * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x15 failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    freeWiseScriptUnknown0x15(data);
    printError("readWiseScriptUnknown0x15 failed to read unknownString_2: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x16(FILE * fp, WiseScriptUnknown0x16 * data) {
  REWError status;

  // init struct
  data->name = NULL;

  status = readString(fp, &data->name);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x16 failed to read name: "
               "%d\n", status);
    return status;
  }
  return REWERR_OK;
}


REWError readWiseScriptUnknown0x17(FILE * fp, WiseScriptUnknown0x17 * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readBytesInto(fp, data->unknown_4, 4);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x17 failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x1C(FILE * fp, WiseScriptUnknown0x1C * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x1C failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  return REWERR_OK;
}


REWError readWiseScriptUnknown0x1E(FILE * fp, WiseScriptUnknown0x1E * data) {
  REWError status;
  status = readBytesInto(fp, data->unknown_2, 2);
  if (status != REWERR_OK) {
    return status;
  }
  return REWERR_OK;
}


REWError readWiseScriptUnknown0x23(FILE * fp, WiseScriptUnknown0x23 * data) {
  REWError status;

  // init struct
  data->unknownString_1 = NULL;
  data->unknownString_2 = NULL;

  status = readBytesInto(fp, &data->unknown_1, 1);
  if (status != REWERR_OK) {
    return status;
  }

  status = readString(fp, &data->unknownString_1);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x23 failed to read unknownString_1: "
               "%d\n", status);
    return status;
  }

  status = readString(fp, &data->unknownString_2);
  if (status != REWERR_OK) {
    printError("readWiseScriptUnknown0x23 failed to read unknownString_2: "
               "%d\n", status);
    freeWiseScriptUnknown0x23(data);
    return status;
  }

  return REWERR_OK;
}




void freeWiseScriptHeader(WiseScriptHeader * header) {
  if (header->logPath != NULL) {
    free(header->logPath);
    header->logPath = NULL;
  }
  if (header->font != NULL) {
    free(header->font);
    header->font = NULL;
  }
}


void freeWiseScriptTexts(WiseScriptTexts * texts) {
  for (uint32_t i = 0; i < 56; i++) {
    if (texts->installTexts[i] != NULL) {
      free(texts->installTexts[i]);
      texts->installTexts[i] = NULL;
    }
  }
}


void freeWiseScriptFileHeader(WiseScriptFileHeader * data) {
  if (data->destFile != NULL) {
    free(data->destFile);
    data->destFile = NULL;
  }
  if (data->fileText != NULL) {
    free(data->fileText);
    data->fileText = NULL;
  }
}


void freeWiseScriptUnknown0x03(WiseScriptUnknown0x03 * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
}


void freeWiseScriptUnknown0x04(WiseScriptUnknown0x04 * data) {
  if (data->dataString != NULL) {
    free(data->dataString);
    data->dataString = NULL;
  }
}


void freeWiseScriptUnknown0x05(WiseScriptUnknown0x05 * data) {
  if (data->file != NULL) {
    free(data->file);
    data->file = NULL;
  }
  if (data->section != NULL) {
    free(data->section);
    data->section = NULL;
  }
  if (data->values != NULL) {
    free(data->values);
    data->values = NULL;
  }
}


void freeWiseScriptUnknown0x07(WiseScriptUnknown0x07 * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
  if (data->unknownString_3 != NULL) {
    free(data->unknownString_3);
    data->unknownString_3 = NULL;
  }
}


void freeWiseScriptUnknown0x09(WiseScriptUnknown0x09 * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
  if (data->unknownString_3 != NULL) {
    free(data->unknownString_3);
    data->unknownString_3 = NULL;
  }
  if (data->unknownString_4 != NULL) {
    free(data->unknownString_4);
    data->unknownString_4 = NULL;
  }
  if (data->unknownString_5 != NULL) {
    free(data->unknownString_5);
    data->unknownString_5 = NULL;
  }
}


void freeWiseScriptUnknown0x0A(WiseScriptUnknown0x0A * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
  if (data->unknownString_3 != NULL) {
    free(data->unknownString_3);
    data->unknownString_3 = NULL;
  }
}


void freeWiseScriptUnknown0x0B(WiseScriptUnknown0x0B * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
}


void freeWiseScriptUnknown0x0C(WiseScriptUnknown0x0C * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
}


void freeWiseScriptUnknown0x11(WiseScriptUnknown0x11 * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
}


void freeWiseScriptUnknown0x12(WiseScriptUnknown0x12 * data) {
  if (data->sourceFile != NULL) {
    free(data->sourceFile);
    data->sourceFile = NULL;
  }
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
  if (data->destFile != NULL) {
    free(data->destFile);
    data->destFile = NULL;
  }
}


void freeWiseScriptUnknown0x14(WiseScriptUnknown0x14 * data) {
  if (data->name != NULL) {
    free(data->name);
    data->name = NULL;
  }
  if (data->message != NULL) {
    free(data->message);
    data->message = NULL;
  }
}


void freeWiseScriptUnknown0x15(WiseScriptUnknown0x15 * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
}


void freeWiseScriptUnknown0x16(WiseScriptUnknown0x16 * data) {
  if (data->name != NULL) {
    free(data->name);
    data->name = NULL;
  }
}


void freeWiseScriptUnknown0x17(WiseScriptUnknown0x17 * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
}


void freeWiseScriptUnknown0x1C(WiseScriptUnknown0x1C * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
}


void freeWiseScriptUnknown0x23(WiseScriptUnknown0x23 * data) {
  if (data->unknownString_1 != NULL) {
    free(data->unknownString_1);
    data->unknownString_1 = NULL;
  }
  if (data->unknownString_2 != NULL) {
    free(data->unknownString_2);
    data->unknownString_2 = NULL;
  }
}




// Debug prints //

// https://www.doubleblak.com/m/blogPosts.php?id=7
// MS-Dos FileTime
// DATE
// ----
// bit  0 -  6   Year
// bit  7 - 10   Month
// bit 11 - 15   Day
//
// TIME
// ----
// bit  0 -  4   Hour
// bit  5 - 10   Minutes
// bit 11 - 15   * 2 Seconds
void printDatetime(uint16_t date, uint16_t time) {
  printf("%04d-%02d-%02d %02d:%02d:%02d",
         (date >> 9) + 1980,
         (date >> 5) & 0b0000000000001111,
         date & 0b0000000000011111,
         (time >> 11),
         (time >> 5) & 0b0000000000111111,
         (time & 0b0000000000011111) * 2);
}

void printHex(unsigned char * value, uint32_t size) {
  for (uint32_t i=0; i < size; i++) {
    printf("%02X", value[i]);
  }
}

void printWiseScriptHeader(WiseScriptHeader * header) {
  printf("WiseScript Header\n-----------------\n");
  for (int i = 0; i < 44; i++) {
    printf("%02X ", header->unknown_44[i]);
  }
  printf("\n");
  printf("'%s': '%s'\n", header->font, header->logPath);
  printf("-----------------\n");
  
}

void printWiseScriptTexts(WiseScriptTexts * texts) {
  printf("WiseScript Texts\n");
  printf("----------------\n");
  for (int i = 0; i < 56; i++) {
    printf("Text: \"%s\"\n", texts->installTexts[i]);
  }
  printf("----------------\n");
}

void printWiseScriptFileHeader(WiseScriptFileHeader * data) {
  printf("0x00 0x%08X 0x%08X ", data->deflateStart, data->deflateEnd);
  printDatetime(data->date, data->time);
  printf(" % 11u ", data->inflatedSize);
  printHex(data->unknown_20, 20);
  printf(" %08X '%s' '%s' %d\n", data->crc32, data->destFile, data->fileText,
         data->terminator);
}

void printWiseScriptUnknown0x03(WiseScriptUnknown0x03 * data) {
  printf("0x03 0x%02X '%s' '%s'\n", data->unknown_1, data->unknownString_1,
         data->unknownString_2);
}

void printWiseScriptUnknown0x04(WiseScriptUnknown0x04 * data) {
  printf("0x04 0x%02X '%s'\n", data->no, data->dataString);
}

void printWiseScriptUnknown0x05(WiseScriptUnknown0x05 * data) {
  printf("0x05 '%s' '%s' '%s'\n", data->file, data->section, data->values);
}

void printWiseScriptUnknown0x06(WiseScriptUnknown0x06 * data) {
  printf("0x06 ");
  printHex(data->unknown, 6);
  printf(" 0x%08X 0x%08X % 11u %02X\n", data->deflateStart, data->deflateEnd,
         data->inflatedSize, data->unknown1);
}

void printWiseScriptUnknown0x07(WiseScriptUnknown0x07 * data) {
  printf("0x07 %02X '%s' '%s' '%s'\n", data->unknown_1, data->unknownString_1,
         data->unknownString_2, data->unknownString_3);
}

void printWiseScriptUnknown0x08(WiseScriptUnknown0x08 * data) {
  printf("0x08 %02X\n", data->unknown_1);
}

void printWiseScriptUnknown0x09(WiseScriptUnknown0x09 * data) {
  printf("0x09 %02X ",  data->unknown_1);
  if (data->unknown_1 != 0x01 && data->unknown_1 != 0x20) {
    printf("%02X ", data->unknown_2);
  }
  printf("'%s' '%s' '%s' '%s'", data->unknownString_1, data->unknownString_2,
         data->unknownString_3, data->unknownString_4);
  if (data->unknown_1 == 0x01 || data->unknown_1 == 0x20) {
    printf(" '%s'", data->unknownString_5);
  }
  printf("\n");
}

void printWiseScriptUnknown0x0A(WiseScriptUnknown0x0A * data) {
  printf("0x0A ");
  printHex(data->unknown_2, 2);
  printf(" '%s' '%s' '%s'\n", data->unknownString_1, data->unknownString_2,
         data->unknownString_3);
}

void printWiseScriptUnknown0x0B(WiseScriptUnknown0x0B * data) {
  printf("0x0B %02X '%s'\n", data->unknown_1, data->unknownString_1);
}

void printWiseScriptUnknown0x0C(WiseScriptUnknown0x0C * data) {
  printf("0x0B %02X '%s' '%s'\n", data->unknown_1, data->unknownString_1,
         data->unknownString_2);
}

void printWiseScriptUnknown0x11(WiseScriptUnknown0x11 * data) {
  printf("0x11 '%s'\n", data->unknownString_1);
}

void printWiseScriptUnknown0x12(WiseScriptUnknown0x12 * data) {
  printf("0x12 %02X ", data->unknown_1);
  printHex(data->unknown_41, 41);
  printf(" '%s' '%s' '%s' '%s'\n", data->sourceFile, data->unknownString_1,
         data->unknownString_2, data->destFile);
}

void printWiseScriptUnknown0x14(WiseScriptUnknown0x14 * data) {
  printf("0x14 0x%08X 0x%08X % 11u '%s' '%s'\n", data->deflateStart,
         data->deflateEnd, data->inflatedSize, data->name, data->message);
}

void printWiseScriptUnknown0x15(WiseScriptUnknown0x15 * data) {
  printf("0x15 %02X '%s' '%s'\n", data->unknown_1, data->unknownString_1,
         data->unknownString_2);
}

void printWiseScriptUnknown0x16(WiseScriptUnknown0x16 * data) {
  printf("0x16 '%s'\n", data->name);
}

void printWiseScriptUnknown0x17(WiseScriptUnknown0x17 * data) {
  printf("0x17 %02X ", data->unknown_1);
  printHex(data->unknown_4, 4);
  printf(" '%s'\n", data->unknownString_1);
}

void printWiseScriptUnknown0x1C(WiseScriptUnknown0x1C * data) {
  printf("0x1C '%s'\n", data->unknownString_1);
}

void printWiseScriptUnknown0x1E(WiseScriptUnknown0x1E * data) {
  printf("0x1E ");
  printHex(data->unknown_2, 2);
  printf("\n");
}

void printWiseScriptUnknown0x23(WiseScriptUnknown0x23 * data) {
  printf("0x1C %02X '%s' '%s'\n", data->unknown_1, data->unknownString_1,
         data->unknownString_2);
}




void initWiseScriptCallbacks(WiseScriptCallbacks * callbacks) {
  callbacks->cb_header = NULL;
  callbacks->cb_texts  = NULL;
  callbacks->cb_0x00   = NULL;
  callbacks->cb_0x03   = NULL;
  callbacks->cb_0x04   = NULL;
  callbacks->cb_0x05   = NULL;
  callbacks->cb_0x06   = NULL;
  callbacks->cb_0x07   = NULL;
  callbacks->cb_0x08   = NULL;
  callbacks->cb_0x09   = NULL;
  callbacks->cb_0x0A   = NULL;
  callbacks->cb_0x0B   = NULL;
  callbacks->cb_0x0C   = NULL;
  callbacks->cb_0x0F   = NULL;
  callbacks->cb_0x10   = NULL;
  callbacks->cb_0x11   = NULL;
  callbacks->cb_0x12   = NULL;
  callbacks->cb_0x14   = NULL;
  callbacks->cb_0x15   = NULL;
  callbacks->cb_0x16   = NULL;
  callbacks->cb_0x17   = NULL;
  callbacks->cb_0x1C   = NULL;
  callbacks->cb_0x1E   = NULL;
  callbacks->cb_0x23   = NULL;
}


bool parseWiseScript(const char * filepath, WiseScriptCallbacks * callbacks) {
  FILE * fp;
  REWError status;
  unsigned char op;

  // check if file exists
  if (access(filepath, F_OK) != 0) {
    printError("parseWiseScript input file '%s' not found\n", filepath);
    printError("parseWiseScript errno: %s\n", strerror(errno));
    return false;
  }

  // open the file
  fp = fopen(filepath, "rb");

  // failed to open the file
  if (fp == NULL) {
    printError("parseWiseScript failed to open file '%s'\n", filepath);
    printError("parseWiseScript errno: %s\n", strerror(errno));
    return false;
  }

  // Read the header
  {
    WiseScriptHeader header;
    status = readWiseScriptHeader(fp,  &header);
    if (status != REWERR_OK) {
      printError("parseWiseScript failed to read header. %d\n", status);
      fclose(fp);
      return false;
    }

    // callback
    if (callbacks->cb_header != NULL) {
      (*callbacks->cb_header)(&header);
    }

    // cleanup
    freeWiseScriptHeader(&header);
  }

  // Read the texts
  {
    WiseScriptTexts texts;
    status = readWiseScriptTexts(fp, &texts);
    if (status != REWERR_OK) {
      printError("parseWiseScript failed to read texts. %d\n", status);
      fclose(fp);
      return false;
    }

    // callback
    if (callbacks->cb_texts != NULL) {
      (*callbacks->cb_texts)(&texts);
    }

    // cleanup
    freeWiseScriptTexts(&texts);
  }

  // Read operation and struct
  WiseScriptSTOP = 0;
  while (status == REWERR_OK && WiseScriptSTOP == 0) {
    int ch = fgetc(fp);
    op = (unsigned char)ch;

    if (ch == EOF) {
      break;
    }

    switch (op) {
      case 0x00:
      {
        WiseScriptFileHeader data;
        status = readWiseScriptFileHeader(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x00 != NULL) {
            (*callbacks->cb_0x00)(&data);
          }
          freeWiseScriptFileHeader(&data);
        }
      }
        break;

      case 0x03:
      {
        WiseScriptUnknown0x03 data;
        status = readWiseScriptUnknown0x03(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x03 != NULL) {
            (*callbacks->cb_0x03)(&data);
          }
          freeWiseScriptUnknown0x03(&data);
        }
      }
        break;

      case 0x04:
      {
        WiseScriptUnknown0x04 data;
        status = readWiseScriptUnknown0x04(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x04 != NULL) {
            (*callbacks->cb_0x04)(&data);
          }
          freeWiseScriptUnknown0x04(&data);
        }
      }
        break;

      case 0x05:
      {
        WiseScriptUnknown0x05 data;
        status = readWiseScriptUnknown0x05(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x05 != NULL) {
            (*callbacks->cb_0x05)(&data);
          }
          freeWiseScriptUnknown0x05(&data);
        }
      }
        break;

      case 0x06:
      {
        WiseScriptUnknown0x06 data;
        status = readWiseScriptUnknown0x06(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x06 != NULL) {
            (*callbacks->cb_0x06)(&data);
          }
        }
      }
        break;

      case 0x07:
      {
        WiseScriptUnknown0x07 data;
        status = readWiseScriptUnknown0x07(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x07 != NULL) {
            (*callbacks->cb_0x07)(&data);
          }
          freeWiseScriptUnknown0x07(&data);
        }
      }
        break;

      case 0x08:
      {
        WiseScriptUnknown0x08 data;
        status = readWiseScriptUnknown0x08(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x08 != NULL) {
            (*callbacks->cb_0x08)(&data);
          }
        }
      }
        break;

      case 0x09:
      {
        WiseScriptUnknown0x09 data;
        status = readWiseScriptUnknown0x09(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x09 != NULL) {
            (*callbacks->cb_0x09)(&data);
          }
          freeWiseScriptUnknown0x09(&data);
        }
      }
        break;

      case 0x0A:
      {
        WiseScriptUnknown0x0A data;
        status = readWiseScriptUnknown0x0A(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x0A != NULL) {
            (*callbacks->cb_0x0A)(&data);
          }
          freeWiseScriptUnknown0x0A(&data);
        }
      }
        break;

      case 0x0B:
      {
        WiseScriptUnknown0x0B data;
        status = readWiseScriptUnknown0x0B(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x0B != NULL) {
            (*callbacks->cb_0x0B)(&data);
          }
          freeWiseScriptUnknown0x0B(&data);
        }
      }
        break;

      case 0x0C:
      {
        WiseScriptUnknown0x0C data;
        status = readWiseScriptUnknown0x0C(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x0C != NULL) {
            (*callbacks->cb_0x0C)(&data);
          }
          freeWiseScriptUnknown0x0C(&data);
        }
      }
        break;

      case 0x0F:
        // Start form data?
        if (callbacks->cb_0x0F != NULL) {
          (*callbacks->cb_0x0F)();
        }
        break;

      case 0x10:
        // end form data?
        if (callbacks->cb_0x10 != NULL) {
          (*callbacks->cb_0x10)();
        }
        break;

      case 0x11:
      {
        WiseScriptUnknown0x11 data;
        status = readWiseScriptUnknown0x11(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x11 != NULL) {
            (*callbacks->cb_0x11)(&data);
          }
          freeWiseScriptUnknown0x11(&data);
        }
      }
        break;

      case 0x12:
      {
        WiseScriptUnknown0x12 data;
        status = readWiseScriptUnknown0x12(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x12 != NULL) {
            (*callbacks->cb_0x12)(&data);
          }
          freeWiseScriptUnknown0x12(&data);
        }
      }
        break;

      case 0x14:
      {
        WiseScriptUnknown0x14 data;
        status = readWiseScriptUnknown0x14(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x14 != NULL) {
            (*callbacks->cb_0x14)(&data);
          }
          freeWiseScriptUnknown0x14(&data);
        }
      }
        break;

      case 0x15:
      {
        WiseScriptUnknown0x15 data;
        status = readWiseScriptUnknown0x15(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x15 != NULL) {
            (*callbacks->cb_0x15)(&data);
          }
          freeWiseScriptUnknown0x15(&data);
        }
      }
        break;

      case 0x16:
      {
        WiseScriptUnknown0x16 data;
        status = readWiseScriptUnknown0x16(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x16 != NULL) {
            (*callbacks->cb_0x16)(&data);
          }
          freeWiseScriptUnknown0x16(&data);
        }
      }
        break;

      case 0x17:
      {
        WiseScriptUnknown0x17 data;
        status = readWiseScriptUnknown0x17(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x17 != NULL) {
            (*callbacks->cb_0x17)(&data);
          }
          freeWiseScriptUnknown0x17(&data);
        }
      }
        break;

      // skip tailing zeros
      case 0x18:
        ch = 0x00;
        while (ch != EOF && ch == 0x00) {
          ch = fgetc(fp);
        }
        if (ch != EOF) {
          fseek(fp, -1, SEEK_CUR);
        }
        break;

      case 0x1B:
      case 0x0D:
      case 0x24: // TODO Skip? Only seen in RTCW
      case 0x25: // TODO Skip? Only seen in RTCW
        // Skip this byte
        break;

      case 0x1C:
      {
        WiseScriptUnknown0x1C data;
        status = readWiseScriptUnknown0x1C(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x1C != NULL) {
            (*callbacks->cb_0x1C)(&data);
          }
          freeWiseScriptUnknown0x1C(&data);
        }
      }
        break;

      case 0x1E:
      {
        WiseScriptUnknown0x1E data;
        status = readWiseScriptUnknown0x1E(fp, &data);
        if (status == REWERR_OK && callbacks->cb_0x1E != NULL) {
          (*callbacks->cb_0x1E)(&data);
        }
      }
        break;

      case 0x23:
      {
        WiseScriptUnknown0x23 data;
        status = readWiseScriptUnknown0x23(fp, &data);
        if (status == REWERR_OK) {
          if (callbacks->cb_0x23 != NULL) {
            (*callbacks->cb_0x23)(&data);
          }
          freeWiseScriptUnknown0x23(&data);
        }
      }
        break;

      default:
        printError("parseWiseScript unknown OP: %02X at 0x%08X\n", ch,
                   ftell(fp));
        status = REWERR_NOOPT;
        break;
    }
  }

  fclose(fp);

  if (status != REWERR_OK || WiseScriptSTOP != 0) {
    printError("parseWiseScript OP 0x%02X failed\n", op);
    return false;
  }

  return true;
}


void stopWiseScriptParse(void) {
  WiseScriptSTOP = 1;
}


static WiseScriptParsedInfo WISESCRIPT_PARSED_INFO = {
  .totalInflatedSize  = 0,
  .inflatedSize0x00   = 0,
  .inflatedSize0x06   = 0,
  .inflatedSize0x14   = 0,
  .inflateStartOffset = 0
};

void updateParsedInfo0x00(WiseScriptFileHeader * data) {
  if (data->deflateEnd > WISESCRIPT_PARSED_INFO.inflateStartOffset ) {
    WISESCRIPT_PARSED_INFO.inflateStartOffset = data->deflateEnd;
  }
  WISESCRIPT_PARSED_INFO.totalInflatedSize += data->inflatedSize;
  WISESCRIPT_PARSED_INFO.inflatedSize0x00  += data->inflatedSize;
}

void updateParsedInfo0x06(WiseScriptUnknown0x06 * data) {
  if (data->deflateEnd > WISESCRIPT_PARSED_INFO.inflateStartOffset ) {
    WISESCRIPT_PARSED_INFO.inflateStartOffset  = data->deflateEnd;
  }
  WISESCRIPT_PARSED_INFO.totalInflatedSize += data->inflatedSize;
  WISESCRIPT_PARSED_INFO.inflatedSize0x06  += data->inflatedSize;
}

void updateParsedInfo0x14(WiseScriptUnknown0x14 * data) {
  if (data->deflateEnd > WISESCRIPT_PARSED_INFO.inflateStartOffset ) {
    WISESCRIPT_PARSED_INFO.inflateStartOffset  = data->deflateEnd;
  }
  WISESCRIPT_PARSED_INFO.totalInflatedSize += data->inflatedSize;
  WISESCRIPT_PARSED_INFO.inflatedSize0x14  += data->inflatedSize;
}

WiseScriptParsedInfo * wiseScriptGetParsedInfo(const char * filepath) {
  WiseScriptCallbacks callbacks;
  bool result;

  initWiseScriptCallbacks(&callbacks);
  callbacks.cb_0x00 = &updateParsedInfo0x00;
  callbacks.cb_0x06 = &updateParsedInfo0x06;
  callbacks.cb_0x14 = &updateParsedInfo0x14;

  WISESCRIPT_PARSED_INFO.totalInflatedSize  = 0;
  WISESCRIPT_PARSED_INFO.inflateStartOffset = 0;

  result = parseWiseScript(filepath, &callbacks);
  if (result == false) {
    printError("wiseScriptGetDeflateEnd parsing failed\n");
    return NULL;
  }

  return &WISESCRIPT_PARSED_INFO;
}


bool wiseScriptDebugPrint(const char * filepath) {
  WiseScriptCallbacks callbacks;

  initWiseScriptCallbacks(&callbacks);
  callbacks.cb_header = &printWiseScriptHeader;
  callbacks.cb_texts  = &printWiseScriptTexts;
  callbacks.cb_0x00   = &printWiseScriptFileHeader;
  callbacks.cb_0x03   = &printWiseScriptUnknown0x03;
  callbacks.cb_0x04   = &printWiseScriptUnknown0x04;
  callbacks.cb_0x05   = &printWiseScriptUnknown0x05;
  callbacks.cb_0x06   = &printWiseScriptUnknown0x06;
  callbacks.cb_0x07   = &printWiseScriptUnknown0x07;
  callbacks.cb_0x08   = &printWiseScriptUnknown0x08;
  callbacks.cb_0x09   = &printWiseScriptUnknown0x09;
  callbacks.cb_0x0A   = &printWiseScriptUnknown0x0A;
  callbacks.cb_0x0B   = &printWiseScriptUnknown0x0B;
  callbacks.cb_0x0C   = &printWiseScriptUnknown0x0C;
  callbacks.cb_0x11   = &printWiseScriptUnknown0x11;
  callbacks.cb_0x12   = &printWiseScriptUnknown0x12;
  callbacks.cb_0x14   = &printWiseScriptUnknown0x14;
  callbacks.cb_0x15   = &printWiseScriptUnknown0x15;
  callbacks.cb_0x16   = &printWiseScriptUnknown0x16;
  callbacks.cb_0x17   = &printWiseScriptUnknown0x17;
  callbacks.cb_0x1C   = &printWiseScriptUnknown0x1C;
  callbacks.cb_0x1E   = &printWiseScriptUnknown0x1E;
  callbacks.cb_0x23   = &printWiseScriptUnknown0x23;

  return parseWiseScript(filepath, &callbacks);
}


// Must be a valid pointer to a \0 terminated string.
char * wiseScriptParsePath(char * path) {
  char * pathCopy;
  char * section;
  char newPath[PATH_MAX];
  char * returnPath;
  uint32_t strSize = 0;

  // Basic verification that this string may be a valid path
  do {
    unsigned char ch = path[strSize];

    if (ch == 0x00) {
      break;
    }

    // It contains a illegal character
    if (ch < 0x20 || ch > 0x7E || ch == '/') {
      printError("wiseScriptParsePath path contains an illegal character "
                 "0x%02X\n", ch);
      return NULL;
    }

    strSize++;

    // Path is to long
    if (strSize > WIN_PATH_MAX) {
      printError("wiseScriptParsePath path is larger then WIN_PATH_MAX\n");
      return NULL;
    }

  } while (1);

  // Check that the path starts with '%'
  if (path[0] != 0x25) {
    printError("wiseScriptParsePath path does not start with '%'\n");
    return NULL;
  }

  // Duplicate the path for the use with strtok
  pathCopy = strdup(path);
  if (pathCopy == NULL) {
    printError("wiseScriptParsePath errno: %s\n", strerror(errno));
    return NULL;
  }

  newPath[0] = 0x00;
  section = strtok(pathCopy, "\\");
  do {
    size_t sectionLen = strlen(section);

    // Strip '%' from variables
    if (section[0] == 0x25 && section[sectionLen - 1] == 0x25) {
      section[sectionLen - 1] = 0x00;
      section++;

      if (section[0] == 0x00) {
        printError("wiseScriptParsePath empty path component.\n");
        free(pathCopy);
        return NULL;
      }
    }

    if (sectionLen > NAME_MAX) {
      printError("wiseScriptParsePath path component name exceeds NAME_MAX\n");
      free(pathCopy);
      return NULL;
    }

    // Don't allow a path section to start with '..'
    if (sectionLen >= 2) {
      if (section[0] == '.' && section[1] == '.') {
        printError("wiseScriptParsePath path component starts with '..'."
                   " Symbolic paths are not allowed! Path: '%s'\n", path);
        free(pathCopy);
        return NULL;
      }
    }

    strcat(newPath, "/");
    strcat(newPath, section);
  } while ((section = strtok(NULL, "\\")));


  free(pathCopy);

  returnPath = strdup(newPath + 1); // +1 to remove the first character '/'

  if (returnPath == NULL) {
    printError("wiseScriptParsePath errno: %s\n", strerror(errno));
    return NULL;
  }

  return returnPath;
}
